import localforage from "localforage";
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react";

const setters_map = new Map<string, Set<(any: any) => void>>();

export function set_forage_item<T = {}>(storage: LocalForage, key: string, data: T) {
  const { name, storeName } = storage.config()
  const full_name = [key, name, storeName].join('##');
  setters_map.get(full_name)?.forEach(fn => fn(data));
  return storage.setItem<T>(key, data);
}
export function del_forage_item(storage: LocalForage, key: string) {
  const { name, storeName } = storage.config()
  const full_name = [key, name, storeName].join('##');
  setters_map.get(full_name)?.forEach(fn => fn(null));
  return storage.removeItem(key);
}


export interface IUseLocalForageOptions extends LocalForageOptions {
  storage?: LocalForage;
}
export function useLocalForage<T = {}>(opts: IUseLocalForageOptions): [
  LocalForage,
  (key: string) => Promise<null | T>,
  (key: string, data: T) => Promise<T>,
  (key: string) => Promise<void>,
] {
  const { version, name, size, storeName, description, driver, storage } = opts;
  const ref_storage = useRef<LocalForage | undefined>(void 0)
  const _storage = useMemo(() => {
    if (storage) return ref_storage.current = storage;
    return ref_storage.current = localforage.createInstance(opts);
  }, [version, name, size, storeName, description, driver, storage])

  const getItem = useCallback((key: string) => _storage.getItem<T>(key), [_storage])

  const setItem = useCallback((key: string, data: T) => {
    return set_forage_item<T>(_storage, key, data);
  }, [_storage])

  const removeItem = useCallback((key: string) => {
    return del_forage_item(_storage, key)
  }, [_storage])

  return [_storage, getItem, setItem, removeItem];
}

export interface IUseLocalForageItemOptions<T = {}> extends IUseLocalForageOptions {
  key: string;
  init?: T | (() => T) | null
}

export function useLocalForageItem<T = {}>(opts: IUseLocalForageItemOptions<T>) {
  const { key, init = null } = opts;
  const [ready, set_ready] = useState<boolean>(false)
  const [value, set_value] = useState<T | null>(init)
  const [storage, getItem, setItem, delItem] = useLocalForage<T>(opts)
  const { name, storeName } = storage.config();
  const full_name = [key, name, storeName].join('##');
  const ref_ready = useRef(false)
  useMemo(() => {
    let setters = setters_map.get(full_name)
    if (!setters) setters_map.set(full_name, setters = new Set())
    setters.add(set_value)
  }, [full_name])

  useEffect(() => {
    let setters = setters_map.get(full_name)
    if (!setters) setters_map.set(full_name, setters = new Set())
    setters.add(set_value)
    return () => { setters.delete(set_value) }
  }, [set_value, full_name])

  useEffect(() => {
    getItem(key)
      .then(v => set_value(v))
      .catch(e => console.warn(`[useLocalForage2] failed to getItem, e:`, e))
      .finally(() => {
        ref_ready.current = true
        set_ready(true)
      })
  }, [getItem, key])

  const _set_value: Dispatch<SetStateAction<T | null>> = useCallback((v: SetStateAction<T | null>) => {
    if (!ref_ready.current) return;
    set_value((prev) => {
      const ret = typeof v === 'function' ? (v as any)(prev) : v;
      if (ret == null || ret == undefined) delItem(key)
      else setItem(key, ret)
      return ret
    });
  }, [key, set_value])

  return [value, _set_value, ready] as const;
}